/*
 * Written and copyright 2001-2003 Tobias Minich. Distributed under the GNU
 * General Public License; see the README file. This code comes with NO
 * WARRANTY.
 * 
 * Set.java
 * 
 * Created on 23.03.2004
 *
 */
package org.gudy.azureus2.ui.console.commands;

import java.io.PrintStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;

import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.util.SHA1Hasher;
import org.gudy.azureus2.ui.common.ExternalUIConst;
import org.gudy.azureus2.ui.console.ConsoleInput;
import org.pf.text.StringPattern;

/**
 * command that allows manipulation of Azureus' runtime properties. - when called without any parameters, it lists all of the available runtime
 * properties. - when called with 1 parameter, it shows the current value of that parameter - when called with 2 or 3 parameters, it assigns a
 * specified value to the specified parameter name. (the third parameter forces the property to be set to a particular type, otherwise we try and
 * guess the type by the current value)
 * 
 * @author Tobias Minich, Paul Duran
 */
public class Set extends IConsoleCommand {

    private static final String NULL_STRING = "__NULL__";

    public Set() {
        super("set", "+");
    }

    public String getCommandDescriptions() {
        return ("set [options] [parameter] [value]\t\t+\tSet a configuration parameter. Use \"param name\" when the name includes a space. If value is omitted, the current setting is shown. Parameter may be a wildcard to narrow results");
    }

    public void printHelpExtra(PrintStream out, List args) {
        out.println("> -----");
        out.println("'set' common parameter abbreviations: ");
        out.println("\tmax_up: Maximum upload speed in KB/sec"); // see ExternalUIConst for the mappings for these
        out.println("\tmax_down: Maximum download speed in KB/sec");
        out.println("'set' options: ");
        out.println("\t-export\t\tPrints all the options with non-defaut values.");
    }

    public void execute(String commandName, ConsoleInput ci, List args) {

        boolean non_defaults = false;

        Iterator it = args.iterator();

        while (it.hasNext()) {
            String arg = (String) it.next();
            if (arg.equals("-export")) {
                non_defaults = true;
                it.remove();
            }
        }
        if (args.isEmpty()) {
            displayOptions(ci.out, new StringPattern("*"), non_defaults);
            return;
        }
        String external_name = (String) args.get(0);
        String internal_name = (String) ExternalUIConst.parameterlegacy.get(external_name);
        if (internal_name == null || internal_name.length() == 0) {
            internal_name = external_name;
        }
        // else
        // ci.out.println("> converting " + origParamName + " to " + parameter);

        Parameter param;
        switch (args.size()) {
            case 1:
                // allow wildcards : eg: Core* or *DHT* to shorten result list
                StringPattern sp = new StringPattern(internal_name);
                if (sp.hasWildcard()) {
                    displayOptions(ci.out, sp, non_defaults);
                } else {
                    // try to display the value of the specified parameter
                    if (!COConfigurationManager.doesParameterDefaultExist(internal_name)) {
                        ci.out.println("> Command 'set': Parameter '" + external_name + "' unknown.");
                        return;
                    }
                    param = Parameter.get(internal_name, external_name);

                    ci.out.println(param.getString(false));
                }
                break;
            case 2:
            case 3:
                String setto = (String) args.get(1);
                String type;
                if (args.size() == 2) {
                    // guess the parameter type by getting the current value and determining its type
                    param = Parameter.get(internal_name, external_name);
                    type = param.getType();
                } else
                    type = (String) args.get(2);

                boolean success = false;
                if (type.equalsIgnoreCase("int") || type.equalsIgnoreCase("integer")) {
                    COConfigurationManager.setParameter(internal_name, Integer.parseInt(setto));
                    success = true;
                } else if (type.equalsIgnoreCase("bool") || type.equalsIgnoreCase("boolean")) {

                    boolean value;

                    if (setto.equalsIgnoreCase("true") || setto.equalsIgnoreCase("y") || setto.equals("1")) {
                        value = true;
                    } else {
                        value = false;
                    }

                    COConfigurationManager.setParameter(internal_name, value);
                    success = true;
                } else if (type.equalsIgnoreCase("float")) {
                    COConfigurationManager.setParameter(internal_name, Float.parseFloat(setto));
                    success = true;
                } else if (type.equalsIgnoreCase("string")) {
                    COConfigurationManager.setParameter(internal_name, setto);
                    success = true;
                } else if (type.equalsIgnoreCase("password")) {
                    SHA1Hasher hasher = new SHA1Hasher();

                    byte[] password = setto.getBytes();

                    byte[] encoded;

                    if (password.length > 0) {

                        encoded = hasher.calculateHash(password);

                    } else {

                        encoded = password;
                    }

                    COConfigurationManager.setParameter(internal_name, encoded);

                    success = true;
                }

                if (success) {
                    COConfigurationManager.save();
                    ci.out.println("> Parameter '" + external_name + "' set to '" + setto + "'. [" + type + "]");
                } else
                    ci.out.println("ERROR: invalid type given");

                break;
            default:
                ci.out.println("Usage: 'set \"parameter\" value type', where type = int, bool, float, string, password");
                break;
        }
    }

    private void displayOptions(PrintStream out, StringPattern sp, boolean non_defaults) {
        sp.setIgnoreCase(true);
        Iterator I =
                non_defaults ? COConfigurationManager.getDefinedParameters().iterator() : COConfigurationManager.getAllowedParameters().iterator();
        Map backmap = new HashMap();
        for (Iterator iter = ExternalUIConst.parameterlegacy.entrySet().iterator(); iter.hasNext();) {
            Map.Entry entry = (Map.Entry) iter.next();
            backmap.put(entry.getValue(), entry.getKey());
        }
        TreeSet srt = new TreeSet();
        while (I.hasNext()) {
            String internal_name = (String) I.next();

            String external_name = (String) backmap.get(internal_name);

            if (external_name == null) {

                external_name = internal_name;
            }
            if (sp.matches(external_name)) {
                Parameter param = Parameter.get(internal_name, external_name);

                if (non_defaults) {

                    if (!param.isDefault()) {

                        srt.add(param.getString(true));
                    }
                } else {

                    srt.add(param.getString(false));
                }
            }
        }
        I = srt.iterator();
        while (I.hasNext()) {
            out.println((String) I.next());
        }
    }

    /**
     * class that represents a parameter. we can use one of these objects to verify a parameter's type and value as well as whether or not a value has
     * been set.
     * 
     * @author pauld
     */
    private static class Parameter {
        private static final int PARAM_INT = 1;
        private static final int PARAM_BOOLEAN = 2;
        private static final int PARAM_STRING = 4;
        private static final int PARAM_OTHER = 8;

        /**
         * returns a new Parameter object reprenting the specified parameter name
         * 
         * @param parameter
         * @return
         */
        public static Parameter get(String internal_name, String external_name) {
            int underscoreIndex = external_name.indexOf('_');
            int nextchar = external_name.charAt(underscoreIndex + 1);

            if (internal_name != external_name && "ibs".indexOf(nextchar) >= 0) {

                try {
                    if (nextchar == 'i') {
                        int value = COConfigurationManager.getIntParameter(internal_name, Integer.MIN_VALUE);
                        return new Parameter(internal_name, external_name, value == Integer.MIN_VALUE ? (Integer) null : new Integer(value));
                    } else if (nextchar == 'b') {
                        // firstly get it as an integer to make sure it is actually set to something
                        if (COConfigurationManager.getIntParameter(internal_name, Integer.MIN_VALUE) != Integer.MIN_VALUE) {
                            boolean b = COConfigurationManager.getBooleanParameter(internal_name);
                            return new Parameter(internal_name, external_name, Boolean.valueOf(b));
                        } else {
                            return new Parameter(internal_name, external_name, (Boolean) null);
                        }
                    } else {
                        String value = COConfigurationManager.getStringParameter(internal_name, NULL_STRING);
                        return new Parameter(internal_name, external_name, NULL_STRING.equals(value) ? null : value);
                    }
                } catch (Throwable e) {

                }
            }

            Object v = COConfigurationManager.getParameter(internal_name);

            try {
                if (v instanceof Long || v instanceof Integer) {

                    int value = COConfigurationManager.getIntParameter(internal_name, Integer.MIN_VALUE);

                    return new Parameter(internal_name, external_name, value == Integer.MIN_VALUE ? (Integer) null : new Integer(value));

                } else if (v instanceof Boolean) {

                    boolean value = COConfigurationManager.getBooleanParameter(internal_name);

                    return new Parameter(internal_name, external_name, Boolean.valueOf(value));

                } else if (v instanceof String || v instanceof byte[]) {

                    String value = COConfigurationManager.getStringParameter(internal_name);

                    return new Parameter(internal_name, external_name, NULL_STRING.equals(value) ? null : value);
                } else {

                    return new Parameter(internal_name, external_name, v, PARAM_OTHER);
                }
            } catch (Throwable e2) {

                return new Parameter(internal_name, external_name, v, PARAM_OTHER);
            }
        }

        public Parameter(String iname, String ename, Boolean val) {
            this(iname, ename, val, PARAM_BOOLEAN);
        }

        public Parameter(String iname, String ename, Integer val) {
            this(iname, ename, val, PARAM_INT);
        }

        public Parameter(String iname, String ename, String val) {
            this(iname, ename, val, PARAM_STRING);
        }

        private Parameter(String _iname, String _ename, Object _val, int _type) {
            type = _type;
            iname = _iname;
            ename = _ename;
            value = _val;
            isSet = (value != null);

            if (!isSet) {

                def = COConfigurationManager.getDefault(iname);

                if (def != null) {

                    if (def instanceof Long) {

                        type = PARAM_INT;
                    }
                }
            }
        }

        private int type;
        private String iname;
        private String ename;
        private Object value;
        private boolean isSet;
        private Object def;

        public String getType() {
            switch (type) {
                case PARAM_BOOLEAN:
                    return "bool";
                case PARAM_INT:
                    return "int";
                case PARAM_STRING:
                    return "string";
                default:
                    return "unknown";
            }
        }

        public boolean isDefault() {
            return (!isSet);
        }

        public String getString(boolean set_format) {
            if (isSet) {
                if (set_format) {

                    return ("set " + quoteIfNeeded(ename) + " " + quoteIfNeeded(value.toString()) + " " + getType());

                } else {

                    return "> " + ename + ": " + value + " [" + getType() + "]";
                }
            } else {
                if (def == null) {

                    return "> " + ename + " is not set. [" + getType() + "]";

                } else {
                    return "> " + ename + " is not set. [" + getType() + ", default: " + def + "]";
                }
            }
        }

        protected String quoteIfNeeded(String str) {
            if (str.indexOf(' ') == -1) {

                return (str);
            }

            return ("\"" + str + "\"");
        }
    }
}
